﻿//Copyright (C) <2013>  <jonathan cleeve norton> All Rights Reserved 
//Contact jon.norton@fin-plus.co.uk website <http://www.fin-plus.co.uk/>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Reflection;
using System.Collections.ObjectModel;
using System.ComponentModel;
using FinPlusCommon;
using System.IO;
using System.Text.RegularExpressions;
using System.Diagnostics;
using FinPlusComponents;
using u = FinPlusUtility.Utility;
using log = Logger.Logger;

namespace FinPlusWPFControls
{
    public enum ConfigType {client, service, state, query, data, result, cmds, expected };

    public partial class FinPlusUnitTest : FinPlusControl
    {
        public override string[] States { get { return new string[] { "TestPath", "Height", "Width", "Left", "Top" }; } }
        public override string[] TestStates { get { return new string[] { "TestString", "TestPath", "IsValid" }; } }
      
        public string TestPath { get { return _testPath; } set { tFilePath.Text = _testPath = value; } }

        private const string NotepadShortcut = @"notepad.lnk"; 
        private const string XmlpadShortcut = @"xmlpad.lnk";
        private const string DeleteFormat = ".yyyyMMdd_hhmmss_fff";
        private const string AssemblyServiceComponents = "finpluscomponents.dll";
        private const string AssemblyClientComponents = "finpluswpfcontrols.dll";
        private const string XmlNode = "root/current";
        private TestConfigs _testConfigs = new TestConfigs();
        private readonly string _path;
        private string _testPath;

        //construct
        public FinPlusUnitTest()
        {
            InitializeComponent();
            var g = new ListCollectionView(_testConfigs);
            g.GroupDescriptions.Add(new PropertyGroupDescription("TestName"));
            dataGridTests.ItemsSource = g;
            _path = u.GetAssemblyPath(Assembly.GetEntryAssembly());
            IsValid = true;
        }

        //common control interface
        public override void CmdExecute(IFunction command)
        {
            try
            {
                CmdCapture(command);

                switch ((Cmds)Enum.Parse(typeof(Cmds), command.Name, true))
                {
                    case Cmds.Folder: FolderLoad(command["Tag"].ToString()); break;
                    case Cmds.PlayTest: PlayTest(command["Tag"].ToString()); break;
                    case Cmds.PlayView: PlayView(command["Tag"].ToString()); break;
                    case Cmds.PlayAndCheck: PlayAndCheck(command["Tag"].ToString()); break;
                    case Cmds.DatabaseData: DatabaseData(command["Tag"].ToString()); break;
                    case Cmds.RecordEvents: RecordEvents(command["Tag"].ToString()); break;
                    case Cmds.SyncResult: SyncResult(command["Tag"].ToString()); break;
                    case Cmds.CheckResult: CheckResult(command["Tag"].ToString()); break;
                    case Cmds.Reset: _testConfigs.Reset(); break;
                    case Cmds.Refresh: Refresh(); break;
                    case Cmds.Clear: _testConfigs.Clear(); break;
                    case Cmds.PlayAll: PlayAll(); break;
                    case Cmds.TestAll: TestAll(); break;
                    case Cmds.Xmlpad: Xmlpad(command["Tag"].ToString()); break;
                    case Cmds.Notepad: Notepad(command["Tag"].ToString()); break;
                    case Cmds.CloneTest: CloneTest(command["Tag"].ToString()); break;
                    case Cmds.EraseTest: EraseTest(command["Tag"].ToString()); break;
                    case Cmds.EraseFile: EraseFile(command["Tag"].ToString()); break;
                    case Cmds.SvnDiff: SvnDiff(command["Tag"].ToString()); break;
                    case Cmds.SvnAdd: SvnAdd(command["Tag"].ToString()); break;
                    case Cmds.SvnCommit: SvnCommit(command["Tag"].ToString()); break;
                    default: throw new Exception(string.Format("event not recognised {0}", command.Name));
                }
            }
            catch (CmdExecuteException e)
            {
                IsValid = log.Log(Logger.Level.Warning, Id, Config, e.Message, e);
            }
        }

        //private
        private void FolderLoad(string testPath)
        {
            tFilePath.Text = _testPath = testPath;
            Refresh();
        }

        private void PlayTest(string testName)
        {
            try
            {
                var testType = _testConfigs.GetTestType(testName);

                if (testType == ConfigType.client)
                    PlayClient(testName);
                else if (testType == ConfigType.service)
                    PlayService(testName);
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void PlayAndCheck(string testName)
        {
            try
            {
                PlayTest(testName);
                CheckResult(testName);
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void PlayView(string testName)
        {
            var testType = _testConfigs.GetTestType(testName);

            if (testType != ConfigType.client) return;

            //get files 
            var client = _testConfigs.GetTestConfig(testName, ConfigType.client);
            var data = _testConfigs.GetTestConfig(testName, ConfigType.data);

            //get client
            var clientFuncs = new Functions(client.FilePath, XmlNode, data != null ? "$datafile$," + data.FilePath : "");
            var clientParentFunc = FinPlusDInject.Get.Construct(clientFuncs, client.FuncName, u.Env.dev);

            //inject states
            var states = _testConfigs.GetTestConfig(testName, ConfigType.state);
            if (states != null) FinPlusDInject.Get.InjectStates(clientParentFunc, new Functions(states.FilePath, XmlNode));

            var clientWindow = new FinPlusWindow();
            clientWindow.Dock(clientParentFunc.Object, client.FuncName);
            clientWindow.Show();
        }

        private void PlayService(string testName)
        {
            try
            {
                //get files
                var service = _testConfigs.GetTestConfig(testName, ConfigType.service);
                var data = _testConfigs.GetTestConfig(testName, ConfigType.data);
                var expected = _testConfigs.GetTestConfig(testName, ConfigType.expected);

                //get res
                var res = _testConfigs.CloneTestConfig(service, ConfigType.result, _testPath);
                expected.FirstDiff = res.FirstDiff = service.FirstDiff = 0;

                //get service
                var serviceFuncs = new Functions(service.FilePath, XmlNode, "$datafile$," + data.FilePath);
                var serviceParentFunc = (FinPlusComponent)FinPlusDInject.Get.Construct(serviceFuncs, service.FuncName, u.Env.dev).Object;

                //save service
                serviceParentFunc.Adaptor.ToXmlDoc().Save(res.FilePath);
                res.LastWrite = DateTime.Now;
                service.FirstDiff = -1;
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void PlayClient(string testName)
        {
            try
            {
                //get files 
                var client = _testConfigs.GetTestConfig(testName, ConfigType.client);
                var data = _testConfigs.GetTestConfig(testName, ConfigType.data);
                var expected = _testConfigs.GetTestConfig(testName, ConfigType.expected);
                
                //get res
                var result = _testConfigs.CloneTestConfig(client, ConfigType.result, _testPath);
                expected.FirstDiff = result.FirstDiff = client.FirstDiff = 0;

                //get client
                var clientFuncs = new Functions(client.FilePath, XmlNode, data != null ? "$datafile$," + data.FilePath : "");
                var clientParentFunc = FinPlusDInject.Get.Construct(clientFuncs, client.FuncName, u.Env.dev);

                //inject states
                //var states = _testConfigs.GetTestConfig(testName, ConfigType.state);
                //if (states != null) FinPlusFuncFactory.InjectState(clientParentFunc, new Functions(states.FilePath, XmlNode));

                //inject commands
                var events = _testConfigs.GetTestConfig(testName, ConfigType.cmds);
                if (events != null) FinPlusDInject.Get.CmdsExecute(clientParentFunc, new Functions(events.FilePath, XmlNode));
       
                //get state with extra attrubute for values
                var resultFuncs = FinPlusDInject.Get.ExtractStates(clientParentFunc, Environment.UserName, true);
                resultFuncs.ToXmlDoc().Save(result.FilePath);

                result.LastWrite = DateTime.Now;
                client.FirstDiff = -1;
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void DatabaseData(string testName)
        {
            try
            {
                var query = _testConfigs.GetTestConfig(testName, ConfigType.query);
                query.FirstDiff = 0;

                //build
                var funcs = new Functions(query.FilePath, XmlNode);
                var obj = (FinPlusControl)FinPlusDInject.Get.Construct(funcs, query.FuncName, u.Env.dev).Object;
                
                //save
                var data = _testConfigs.CloneTestConfig(query, ConfigType.data, _testPath);
                obj.Adaptor.ToXmlDoc().Save(data.FilePath);
                data.LastWrite = DateTime.Now;
                query.FirstDiff = -1;
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void RecordEvents(string testName)
        {
            try
            {
                if (_testConfigs.GetTestType(testName) != ConfigType.client) return;

                //get files
                var client = _testConfigs.GetTestConfig(testName, ConfigType.client);
                var data = _testConfigs.GetTestConfig(testName, ConfigType.data);
                var evt = _testConfigs.CloneTestConfig(client, ConfigType.cmds, _testPath);
                evt.FirstDiff = 0;

                //get client
                var clientFuncs = new Functions(client.FilePath, XmlNode, data != null ? "$datafile$," + data.FilePath : "");
                var clientObj = (FinPlusControl)FinPlusDInject.Get.Construct(clientFuncs, client.FuncName, u.Env.dev).Object;
                
                //set to record events
                clientObj.Env = u.Env.test;
                clientObj.Commands = new Functions();

                //open client to record events                
                var _finPlusWindow = new FinPlusWindow();
                _finPlusWindow.Dock(clientObj, client.FilePath.TrimEnd(client.FileType.ToCharArray()).TrimEnd('.'));
                _finPlusWindow.ShowDialog();

                //get events
                clientObj.Commands.ToXmlDoc().Save(evt.FilePath);
                clientObj.Commands = null;
                evt.FirstDiff = -1;
                evt.LastWrite = DateTime.Now;
          
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void SyncResult(string testName)
        {
            try
            {
                //get result
                var result = _testConfigs.GetTestConfig(testName, ConfigType.result);
                
                //get expected
                var expected = _testConfigs.CloneTestConfig(result, ConfigType.expected, _testPath);
                
                //copy
                File.Copy(result.FilePath, expected.FilePath, true);
                expected.LastWrite = DateTime.Now;
                result.FirstDiff = 0;
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void CloneTest(string testName)
        {
            try
            {
                var name = _testConfigs.NextTestName(testName);
                var configsToClone = _testConfigs.Where(f => f.TestName == testName).ToList();

                foreach (var configToClone in configsToClone)
                {
                    string path = string.Format("{0}\\{1}.{2}.{3}", _testPath, name, configToClone.FuncName, configToClone.FileType);
                    File.Copy(configToClone.FilePath, path, true);
                    _testConfigs.Set(path);
                }
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void EraseTest(string testName)
        {
            try
            {
                if (string.IsNullOrEmpty(testName)) return;
                var configsToDelete = _testConfigs.Where(f => f.TestName == testName).ToList();
                string d = DateTime.Now.ToString(DeleteFormat);

                foreach (var configToDelete in configsToDelete)
                {
                    _testConfigs.Remove(configToDelete);
                    File.Move(configToDelete.FilePath, configToDelete.FilePath + d);
                }
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void EraseFile(string filePath)
        {
            try
            {
                string d = DateTime.Now.ToString(DeleteFormat);
                File.Move(filePath, filePath + d);
                _testConfigs.Remove(filePath);
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void CheckResult(string testName)
        {
            StreamReader fileResult = null;
            StreamReader fileExpected = null;

            string result = string.Empty;
            string expected = string.Empty;
            string lineDiff = string.Empty;

            try
            {
                //get result
                var fileRes = _testConfigs.GetTestConfig(testName, ConfigType.result);
                var funcsRes = new Functions(fileRes.FilePath, XmlNode);

                //get expect
                var fileExp = _testConfigs.GetTestConfig(testName, ConfigType.expected);
                var funcsExp = new Functions(fileExp.FilePath, XmlNode);
                fileExp.FirstDiff = fileRes.FirstDiff = 0;

                int x = funcsExp.Count();
                int r = funcsRes.Count();

                if (x != r)
                    fileExp.FirstDiff = fileRes.FirstDiff = x;

                //compare
                for (int i = 1; i <= x && i <= r; i++)
                {
                    var fres = funcsRes[i];
                    var fexp = funcsExp[i];
                    if (fres.IsEqual(fexp))
                        continue;

                    fileExp.FirstDiff =  fileRes.FirstDiff = i;
                    break;
                }
                
                if(fileExp.FirstDiff == 0)
                    fileExp.FirstDiff = fileRes.FirstDiff = -1;

            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
            finally
            {
                if (fileResult != null) fileResult.Close();
                if (fileExpected != null) fileExpected.Close();
            }
        }

        private void Refresh()
        {
            _testConfigs.Clear();

            var f = new List<string>();
            foreach (var ct in Enum.GetValues(typeof(ConfigType)))
                f.Add(string.Format("*.{0}", ct.ToString()));

            var files = f.SelectMany(t => Directory.GetFiles(_testPath, t, SearchOption.TopDirectoryOnly)).ToArray();
            _testConfigs.Add(files);
        }

        private void PlayAll()
        {
            foreach (var testName in _testConfigs.TestNames())
                PlayTest(testName);
        }

        private void TestAll()
        {
            foreach (var testName in _testConfigs.TestNames())
                CheckResult(testName);
        }

        private void Xmlpad(string filePath)
        {
            try
            {
                Process.Start(_path + XmlpadShortcut, filePath);
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void Notepad(string filePath)
        {
            try
            {
                Process.Start(_path + NotepadShortcut, filePath);
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void SvnDiff(string filePath)
        {
            try
            {
                Process.Start(_path + "TortoiseMerge.lnk", filePath);
                if (string.IsNullOrEmpty(filePath)) 
                    return;
                Process.Start("CMD.exe", @"/k svn diff " + filePath);
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void SvnAdd(string filePath)
        {
            try
            {
                if(string.IsNullOrEmpty(filePath)) return;
                Process.Start("CMD.exe", @"/k svn add " + filePath);
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        private void SvnCommit(string filePath)
        {
            try
            {
                if (string.IsNullOrEmpty(filePath)) return;
                Process.Start("CMD.exe", @"/k svn commit " + filePath);
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        //private pre cmd router
        private void Folder()
        {
            try
            {
                var d = new System.Windows.Forms.FolderBrowserDialog();
                var res = d.ShowDialog();
                if (res == System.Windows.Forms.DialogResult.OK)
                    CmdExecute(new Function(Cmds.Folder.ToString(), Id, User, -1, d.SelectedPath));
            }
            catch (Exception e)
            {
                log.Log(Logger.Level.Error, Id, Config, e.Message, e);
            }
        }

        //cmds
        private enum Cmds { Folder, PlayView, PlayTest, PlayAndCheck, PlayVisual, DatabaseData, RecordEvents, SyncResult, CloneTest, CheckResult, Reset, Refresh, Clear, PlayAll, TestAll, Xmlpad, Notepad, EraseTest, EraseFile, SvnDiff, SvnAdd, SvnCommit }

        //events
        private void Folder_OnClick(object s, RoutedEventArgs a) { Folder(); }//note result of this event goes through router
        private void PlayTest_OnClick(object s, RoutedEventArgs a) { CmdRouter(Cmds.PlayTest.ToString(), s); }
        private void PlayAndCheck_OnClick(object s, RoutedEventArgs a) { CmdRouter(Cmds.PlayAndCheck.ToString(), s); }
        private void PlayView_OnClick(object s, RoutedEventArgs a) { CmdRouter(Cmds.PlayView.ToString(), s); }
        private void DatabaseData_OnClick(object s, RoutedEventArgs a) { CmdRouter(Cmds.DatabaseData.ToString(), s); }
        private void RecordEvents_OnClick(object s, RoutedEventArgs a) { CmdRouter(Cmds.RecordEvents.ToString(), s); }
        private void SyncResult_OnClick(object s, RoutedEventArgs a) { CmdRouter(Cmds.SyncResult.ToString(), s); }
        private void CheckResult_OnClick(object s, RoutedEventArgs a) { CmdRouter(Cmds.CheckResult.ToString(), s); }
        private void Reset_OnClick(object s, RoutedEventArgs a) { CmdRouter(Cmds.Reset.ToString()); }
        private void Refresh_OnClick(object s, RoutedEventArgs a) { CmdRouter(Cmds.Refresh.ToString()); }
        private void Clear_OnClick(object s, RoutedEventArgs a) { CmdRouter(Cmds.Clear.ToString()); }
        private void PlayAll_OnClick(object s, RoutedEventArgs a) { CmdRouter(Cmds.PlayAll.ToString()); }
        private void TestAll_OnClick(object s, RoutedEventArgs a) { CmdRouter(Cmds.TestAll.ToString()); }
        private void Xmlpad_OnClick(object s, RoutedEventArgs a) { CmdRouter(Cmds.Xmlpad.ToString(), s); }
        private void Notepad_OnClick(object s, RoutedEventArgs a) { CmdRouter(Cmds.Notepad.ToString(), s); }
        private void CloneTest_OnClick(object s, RoutedEventArgs a) { CmdRouter(Cmds.CloneTest.ToString(), s); }
        private void EraseTest_OnClick(object s, RoutedEventArgs a) { CmdRouter(Cmds.EraseTest.ToString(), s); }
        private void EraseFile_OnClick(object s, RoutedEventArgs a) { CmdRouter(Cmds.EraseFile.ToString(), s); }
        private void SvnDiff_OnClick(object s, RoutedEventArgs a) { CmdRouter(Cmds.SvnDiff.ToString(), s); }
        private void SvnAdd_OnClick(object s, RoutedEventArgs a) { CmdRouter(Cmds.SvnAdd.ToString(), s); }
        private void SvnCommit_OnClick(object s, RoutedEventArgs a) { CmdRouter(Cmds.SvnCommit.ToString(), s); }
    }

    public sealed class TestConfigs : ObservableCollection<TestConfig>
    {
        public Dictionary<string, TestConfig> Index { get; set; }

        public TestConfig CloneTestConfig(TestConfig from, ConfigType type, string testPath)
        {
            var path = string.Format("{0}\\{1}.{2}.{3}", testPath, from.TestName, from.FuncName, type.ToString()).ToLower();
            return this.Index.ContainsKey(path) ? this.Index[path] : this.Set(path);
        }

        public void Add(string[] files)
        {
            Index = new Dictionary<string, TestConfig>();
            this.Reset();

            foreach (var file in files)
                Set(file);
        }

        public TestConfig Set(string file)
        {
            var t = new  TestConfig(file);
            Index[t.FilePath] = t;
            this.Add(t);
            return t;
        }

        public void Remove(string filePath)
        {
            this.Remove(Index[filePath]);
            Index.Remove(filePath);
        }

        public void Reset()
        {
            foreach (var t in this)
                t.Reset();
        }

        public List<string> TestNames()
        {
            return this.Select(t => t.TestName).Distinct().ToList();
        }

        public string NextTestName(string _testName)
        {
            var nextName = "";
            
            string n = Regex.Match(_testName,@"^[^\d]+").Value;

            for(int x=1; true; x++)
            {
                var t = this.Where(f => f.TestName == n + x).Count();
                if (t > 0) continue;
                nextName = n + x;
                break;
            }
            return nextName;
        }

        public ConfigType GetTestType(string testName)
        {
            var t = this.Where(f => f.TestName == testName && f.FileType == ConfigType.service.ToString());
            if (t.Count() > 0)  return ConfigType.service;
            
            return ConfigType.client;
        }

        public TestConfig GetTestConfig(string testName, ConfigType configType)
        {
            var t = this.Where(f => f.TestName == testName && f.FileType == configType.ToString());
            return t.Count() < 1 ? null : t.First();
        }
    }

    public sealed class TestConfig : INotifyPropertyChanged
    {
        public string TestName { get { return _testName; } }
        public string FileType { get { return _fileType.ToString(); } }
        public string FileName { get { return _fileName; } }
        public string FilePath { get { return _filePath; } }
        public string FuncName { get { return _funcName; } }
        public DateTime LastWrite { get { return _lastWrite; } set { _lastWrite = value; NotifyPropertyChanged("LastWrite"); } }
        public string FirstDiffString { get { return _firstDiff > 0 ? string.Format("Func {0} failed", _firstDiff) : "N/A"; } }
        public int FirstDiff { get { return _firstDiff; } set { _firstDiff = value; NotifyPropertyChanged("FirstDiffString"); NotifyPropertyChanged("FirstDiff"); } }

        public event PropertyChangedEventHandler PropertyChanged;

        private string _testName;
        private ConfigType _fileType;
        private string _fileName;
        private string _filePath;
        private string _funcName;
        private DateTime _lastWrite;
        private int _firstDiff;

        //public
        public TestConfig(string file)
        {
            _fileName = file.Substring(file.LastIndexOf('\\') + 1);
            var p = _fileName.Split('.');
            _testName = p[0];
            _funcName = p[1];
            _fileType = (ConfigType)Enum.Parse(typeof(ConfigType), p[2].ToLower(), true);
            _filePath = file.ToLower();
            Reset();
        }

        public void Reset()
        {
            FirstDiff = _fileType == ConfigType.state || _fileType == ConfigType.cmds || _fileType == ConfigType.query || _fileType == ConfigType.data ? -1 : 0;
            LastWrite = File.GetLastWriteTime(_filePath);
        }

        public void NotifyPropertyChanged(string propName)
        {
            if (PropertyChanged != null) 
                PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }
}
